home *** CD-ROM | disk | FTP | other *** search
/ NeXT Education Software Sampler 1992 Fall / NeXT Education Software Sampler 1992 Fall.iso / Programming / Source / HippoDraw / HippoDrawSrc1.1 / Hippo.subproj / Graphic.m < prev    next >
Encoding:
Text File  |  1992-04-25  |  24.7 KB  |  1,083 lines

  1. #import "Graphic.h"
  2. #import "GraphicView.h"
  3. #import "draw.h"
  4. #import <appkit/Application.h>
  5. #import <appkit/NXCursor.h>
  6. #import <appkit/Window.h>
  7. #import <appkit/defaults.h>
  8. #import <appkit/timer.h>
  9. #import <appkit/nextstd.h>
  10. #import <dpsclient/wraps.h>
  11. #import <math.h>
  12.  
  13. @implementation Graphic : Object
  14.  
  15. static int KNOB_WIDTH = 0.0;
  16. static int KNOB_HEIGHT = 0.0;
  17.  
  18. #define MINSIZE 5.0    /* minimum size of a Graphic */
  19.  
  20. id CrossCursor = nil;    /* global since subclassers may need it */
  21.  
  22. /* Optimization method. */
  23.  
  24. static NXRect *blackRectList = NULL;
  25. static int blackRectSize = 0;
  26. static int blackRectCount = 0;
  27. static NXRect *dkgrayRectList = NULL;
  28. static int dkgrayRectSize = 0;
  29. static int dkgrayRectCount = 0;
  30.  
  31. + fastRectFill:(const NXRect *)aRect isBlack:(BOOL)isBlack
  32. {
  33.     if (!aRect) return self;
  34.  
  35.     if (isBlack) {
  36.     if (!blackRectList) {
  37.         blackRectSize = 16;
  38.         NX_ZONEMALLOC([NXApp zone], blackRectList, NXRect, blackRectSize);
  39.     } else {
  40.         while (blackRectCount >= blackRectSize) blackRectSize <<= 1;
  41.         NX_ZONEREALLOC([NXApp zone], blackRectList, NXRect, blackRectSize);
  42.     }
  43.     blackRectList[blackRectCount++] = *aRect;
  44.     } else {
  45.     if (!dkgrayRectList) {
  46.         dkgrayRectSize = 16;
  47.         NX_ZONEMALLOC([NXApp zone], dkgrayRectList, NXRect, dkgrayRectSize);
  48.     } else {
  49.         while (dkgrayRectCount >= dkgrayRectSize) dkgrayRectSize <<= 1;
  50.         NX_ZONEREALLOC([NXApp zone], dkgrayRectList, NXRect, dkgrayRectSize);
  51.     }
  52.     dkgrayRectList[dkgrayRectCount++] = *aRect;
  53.     }
  54.  
  55.     return self;
  56. }
  57.  
  58. + showFastRectFills
  59. {
  60.     PSsetgray(NX_BLACK);
  61.     NXRectFillList(blackRectList, blackRectCount);
  62.     PSsetgray(NX_DKGRAY);
  63.     NXRectFillList(dkgrayRectList, dkgrayRectCount);
  64.     blackRectCount = 0;
  65.     dkgrayRectCount = 0;
  66.     return self;
  67. }
  68.  
  69. /* Factory methods. */
  70.  
  71. + initialize
  72. /*
  73.  * This bumps the class version so that we can compatibly read
  74.  * old Graphic objects out of an archive.
  75.  */
  76. {
  77.     [self setVersion:1];
  78.     return self;
  79. }
  80.  
  81. + (BOOL)isEditable
  82. /*
  83.  * Any Graphic which can be edited should return YES from this
  84.  * and its instances should do something in the response to the
  85.  * edit:in: method.
  86.  */
  87. {
  88.     return NO;
  89. }
  90.  
  91. + cursor
  92. /*
  93.  * Any Graphic that doesn't have a special cursor gets the default cross.
  94.  */
  95. {
  96.     NXPoint spot;
  97.  
  98.     if (!CrossCursor) {
  99.     CrossCursor = [NXCursor newFromImage:[NXImage newFromSection:"cross.tiff"]];
  100.     spot.x = 7.0; spot.y = 7.0;
  101.     [CrossCursor setHotSpot:&spot];
  102.     }
  103.  
  104.     return CrossCursor;
  105. }
  106.  
  107. static void initClassVars()
  108. {
  109.     const char *value;
  110.     NXCoord w = 2.0, h = 2.0;
  111.  
  112.     if (!KNOB_WIDTH) {
  113.     value = NXGetDefaultValue([NXApp appName], "KnobWidth");
  114.     if (value) w = floor(atof(value) / 2.0);
  115.     value = NXGetDefaultValue([NXApp appName], "KnobHeight");
  116.     if (value) h = floor(atof(value) / 2.0);
  117.     w = MAX(w, 1.0); h = MAX(h, 1.0);
  118.     KNOB_WIDTH = w * 2.0 + 1.0;    /* size must be odd */
  119.     KNOB_HEIGHT = h * 2.0 + 1.0;
  120.     }
  121. }
  122.  
  123. - init
  124. {
  125.     [super init];
  126.     gFlags.active = YES;
  127.     gFlags.selected = YES;
  128.     initClassVars();
  129.     return self;
  130. }
  131.  
  132. - awake
  133. {
  134.     initClassVars();
  135.     return [super awake];
  136. }
  137.  
  138. /* Private C functions and macros used to implement methods in this class. */
  139.  
  140. #define stopTimer(timer) if (timer) { \
  141.     NXEndTimer(timer); \
  142.     timer = NULL; \
  143. }
  144.  
  145. #define startTimer(timer) if (!timer) timer = NXBeginTimer(NULL, 0.1, 0.1);
  146.  
  147. static void drawKnobs(const NXRect *rect, int cornerMask, BOOL black)
  148. /*
  149.  * Draws either the knobs or their shadows (not both).
  150.  */
  151. {
  152.     NXRect knob;
  153.     NXCoord dx, dy;
  154.     BOOL oddx, oddy;
  155.  
  156.     knob = *rect;
  157.     dx = knob.size.width / 2.0;
  158.     dy = knob.size.height / 2.0;
  159.     oddx = (floor(dx) != dx);
  160.     oddy = (floor(dy) != dy);
  161.     knob.size.width = KNOB_WIDTH;
  162.     knob.size.height = KNOB_HEIGHT;
  163.     knob.origin.x -= ((KNOB_WIDTH - 1.0) / 2.0);
  164.     knob.origin.y -= ((KNOB_HEIGHT - 1.0) / 2.0);
  165.  
  166.     if (cornerMask & LOWER_LEFT_MASK) [Graphic fastRectFill:&knob isBlack:black];
  167.     knob.origin.y += dy;
  168.     if (oddy) knob.origin.y -= 0.5;
  169.     if (cornerMask & LEFT_SIDE_MASK) [Graphic fastRectFill:&knob isBlack:black];
  170.     knob.origin.y += dy;
  171.     if (oddy) knob.origin.y += 0.5;
  172.     if (cornerMask & UPPER_LEFT_MASK) [Graphic fastRectFill:&knob isBlack:black];
  173.     knob.origin.x += dx;
  174.     if (oddx) knob.origin.x -= 0.5;
  175.     if (cornerMask & TOP_SIDE_MASK) [Graphic fastRectFill:&knob isBlack:black];
  176.     knob.origin.x += dx;
  177.     if (oddx) knob.origin.x += 0.5;
  178.     if (cornerMask & UPPER_RIGHT_MASK) [Graphic fastRectFill:&knob isBlack:black];
  179.     knob.origin.y -= dy;
  180.     if (oddy) knob.origin.y -= 0.5;
  181.     if (cornerMask & RIGHT_SIDE_MASK) [Graphic fastRectFill:&knob isBlack:black];
  182.     knob.origin.y -= dy;
  183.     if (oddy) knob.origin.y += 0.5;
  184.     if (cornerMask & LOWER_RIGHT_MASK) [Graphic fastRectFill:&knob isBlack:black];
  185.     knob.origin.x -= dx;
  186.     if (oddx) knob.origin.x += 0.5;
  187.     if (cornerMask & BOTTOM_SIDE_MASK) [Graphic fastRectFill:&knob isBlack:black];
  188. }
  189.  
  190. /* Private methods sometimes overridden by subclassers */
  191.  
  192. - setGraphicsState
  193. {
  194.     PSSetParameters(gFlags.linecap, gFlags.linejoin, linewidth);
  195.     return self;
  196. }
  197.  
  198. - setLineColor
  199. {
  200.     if (lineColor) {
  201.     NXSetColor(*lineColor);
  202.     } else {
  203.     PSsetgray(NX_BLACK);
  204.     }
  205.     return self;
  206. }
  207.  
  208. - setFillColor
  209. {
  210.     if (fillColor) NXSetColor(*fillColor);
  211.     return self;
  212. }
  213.  
  214. - (int)cornerMask
  215. /*
  216.  * Returns a mask of the corners which should have a knobby in them.
  217.  */
  218. {
  219.     return ALL_CORNERS;
  220. }
  221.  
  222. /* Public routines mostly called by GraphicView's. */
  223.  
  224. - (BOOL)isSelected
  225. {
  226.     return gFlags.selected;
  227. }
  228.  
  229. - (BOOL)isActive
  230. {
  231.     return gFlags.active;
  232. }
  233.  
  234. - (BOOL)isLocked
  235. {
  236.     return gFlags.locked;
  237. }
  238.  
  239. - select
  240. {
  241.     gFlags.selected = YES;
  242.     return self;
  243. }
  244.  
  245. - deselect
  246. {
  247.     gFlags.selected = NO;
  248.     return self;
  249. }
  250.  
  251. - activate
  252. /*
  253.  * Activation is used to *temporarily* take a Graphic out of the GraphicView.
  254.  */
  255. {
  256.     gFlags.active = YES;
  257.     return self;
  258. }
  259.  
  260. - deactivate
  261. {
  262.     gFlags.active = NO;
  263.     return self;
  264. }
  265.  
  266. - lock
  267. /*
  268.  * A locked graphic cannot be selected, resized or moved.
  269.  */
  270. {
  271.     gFlags.locked = YES;
  272.     return self;
  273. }
  274.  
  275. - unlock
  276. {
  277.     gFlags.locked = NO;
  278.     return self;
  279. }
  280.  
  281. - setCacheable:(BOOL)flag
  282. {
  283.     return self;
  284. }
  285.  
  286. - (BOOL)isCacheable
  287. {
  288.     return YES;
  289. }
  290.  
  291. - getBounds:(NXRect *)theRect
  292. {
  293.     *theRect = bounds;
  294.     return self;
  295. }
  296.  
  297. - setBounds:(const NXRect *)aRect
  298. {
  299.     bounds = *aRect;
  300.     return self;
  301. }
  302.  
  303. - (NXRect *)getExtendedBounds:(NXRect *)theRect
  304. /*
  305.  * Returns, by reference, the rectangle which encloses the Graphic
  306.  * AND ITS KNOBBIES and its increased line width (if appropriate).
  307.  */
  308. {
  309.     if (bounds.size.width < 0.0) {
  310.     theRect->origin.x = bounds.origin.x + bounds.size.width;
  311.     theRect->size.width = - bounds.size.width;
  312.     } else {
  313.     theRect->origin.x = bounds.origin.x;
  314.     theRect->size.width = bounds.size.width;
  315.     }
  316.     if (bounds.size.height < 0.0) {
  317.     theRect->origin.y = bounds.origin.y + bounds.size.height;
  318.     theRect->size.height = - bounds.size.height;
  319.     } else {
  320.     theRect->origin.y = bounds.origin.y;
  321.     theRect->size.height = bounds.size.height;
  322.     }
  323.  
  324.     theRect->size.width = MAX(1.0, theRect->size.width);
  325.     theRect->size.height = MAX(1.0, theRect->size.height);
  326.  
  327.     NXInsetRect(theRect, - ((KNOB_WIDTH - 1.0) + linewidth + 1.0),
  328.              - ((KNOB_HEIGHT - 1.0) + linewidth + 1.0));
  329.  
  330.     if (gFlags.arrow) {
  331.     if (linewidth) {
  332.         NXInsetRect(theRect, - linewidth * 2.5, - linewidth * 2.5);
  333.     } else {
  334.         NXInsetRect(theRect, - 13.0, - 13.0);
  335.     }
  336.     NXIntegralRect(theRect);
  337.     }
  338.  
  339.     return theRect;
  340. }
  341.  
  342. - (int)knobHit:(const NXPoint *)p
  343. /*
  344.  * Returns 0 if point is in bounds, and Graphic isOpaque, and no knobHit.
  345.  * Returns -1 if outside bounds or not opaque or not active.
  346.  * Returns corner number if there is a hit on a corner.
  347.  * We have to be careful when the bounds are off an odd size since the
  348.  * knobs on the sides are one pixel larger.
  349.  */
  350. {
  351.     NXRect eb;
  352.     NXRect knob;
  353.     NXCoord dx, dy;
  354.     BOOL oddx, oddy;
  355.     int cornerMask = [self cornerMask];
  356.  
  357.     [self getExtendedBounds:&eb];
  358.  
  359.     if (!gFlags.active) {
  360.     return -1;
  361.     } else if (!gFlags.selected) {
  362.         return (NXMouseInRect(p, &bounds, NO) && [self isOpaque]) ? 0 : -1;
  363.     } else {
  364.         if (!NXMouseInRect(p, &eb, NO)) return -1;
  365.     }
  366.  
  367.     knob = bounds;
  368.     dx = knob.size.width / 2.0;
  369.     dy = knob.size.height / 2.0;
  370.     oddx = (floor(dx) != dx);
  371.     oddy = (floor(dy) != dy);
  372.     knob.size.width = KNOB_WIDTH;
  373.     knob.size.height = KNOB_HEIGHT;
  374.     knob.origin.x -= ((KNOB_WIDTH - 1.0) / 2.0);
  375.     knob.origin.y -= ((KNOB_HEIGHT - 1.0) / 2.0);
  376.  
  377.     if ((cornerMask & LOWER_LEFT_MASK) && NXMouseInRect(p, &knob, NO))
  378.     return(LOWER_LEFT);
  379.     knob.origin.y += dy;
  380.     if (oddy) knob.origin.y -= 0.5;
  381.     if ((cornerMask & LEFT_SIDE_MASK) && NXMouseInRect(p, &knob, NO))
  382.     return(LEFT_SIDE);
  383.     knob.origin.y += dy;
  384.     if (oddy) knob.origin.y += 0.5;
  385.     if ((cornerMask & UPPER_LEFT_MASK) && NXMouseInRect(p, &knob, NO))
  386.     return(UPPER_LEFT);
  387.     knob.origin.x += dx;
  388.     if (oddx) knob.origin.x -= 0.5;
  389.     if ((cornerMask & TOP_SIDE_MASK) && NXMouseInRect(p, &knob, NO))
  390.     return(TOP_SIDE);
  391.     knob.origin.x += dx;
  392.     if (oddx) knob.origin.x += 0.5;
  393.     if ((cornerMask & UPPER_RIGHT_MASK) && NXMouseInRect(p, &knob, NO))
  394.     return(UPPER_RIGHT);
  395.     knob.origin.y -= dy;
  396.     if (oddy) knob.origin.y -= 0.5;
  397.     if ((cornerMask & RIGHT_SIDE_MASK) && NXMouseInRect(p, &knob, NO))
  398.     return(RIGHT_SIDE);
  399.     knob.origin.y -= dy;
  400.     if (oddy) knob.origin.y += 0.5;
  401.     if ((cornerMask & LOWER_RIGHT_MASK) && NXMouseInRect(p, &knob, NO))
  402.     return(LOWER_RIGHT);
  403.     knob.origin.x -= dx;
  404.     if (oddx) knob.origin.x += 0.5;
  405.     if ((cornerMask & BOTTOM_SIDE_MASK) && NXMouseInRect(p, &knob, NO))
  406.     return(BOTTOM_SIDE);
  407.  
  408.     return NXMouseInRect(p, &bounds, NO) ? ([self isOpaque] ? 0 : -1) : -1;
  409. }
  410.  
  411. - draw:(const NXRect *)rect
  412. /*
  413.  * Draws the graphic inside rect.  If rect is NULL, then it draws the
  414.  * entire Graphic.  If the Graphic is not intersected by rect, then it
  415.  * is not drawn at all.  If the Graphic is selected, it is drawn with
  416.  * its knobbies.  This method is not intended to be overridden.  It
  417.  * calls the overrideable method "draw" which doesn't have to worry
  418.  * about drawing the knobbies.
  419.  */
  420. {
  421.     NXRect r;
  422.  
  423.     [self getExtendedBounds:&r];
  424.     if (gFlags.active && (!rect || NXIntersectsRect(rect, &r))) {
  425.     if ([self isOpaque]) [Graphic showFastRectFills];
  426.     [self setGraphicsState];    /* does a gsave */
  427.     [self draw];
  428.     PSgrestore();            /* so we need a grestore here */
  429.     if (gFlags.selected && NXDrawingStatus == NX_DRAWING) {
  430.         r = bounds;
  431.         r.origin.x += 1.0;
  432.         r.origin.y -= 1.0;
  433.         drawKnobs(&r, [self cornerMask], YES);        /* shadows */
  434.         r = bounds;
  435.         drawKnobs(&r, [self cornerMask], NO);    /* knobs */
  436.     }
  437.     return self;
  438.     }
  439.  
  440.     return nil;
  441. }
  442.  
  443. - moveLeftEdgeTo:(NXCoord *)x
  444. {
  445.     bounds.origin.x = *x;
  446.     return self;
  447. }
  448.  
  449. - moveRightEdgeTo:(NXCoord *)x
  450. {
  451.     bounds.origin.x = *x - bounds.size.width;
  452.     return self;
  453. }
  454.  
  455. - moveTopEdgeTo:(NXCoord *)y
  456. {
  457.     bounds.origin.y = *y - bounds.size.height;
  458.     return self;
  459. }
  460.  
  461. - moveBottomEdgeTo:(NXCoord *)y
  462. {
  463.     bounds.origin.y = *y;
  464.     return self;
  465. }
  466.  
  467. - moveHorizontalCenterTo:(NXCoord *)x
  468. {
  469.     bounds.origin.x = *x - floor(bounds.size.width / 2.0);
  470.     return self;
  471. }
  472.  
  473. - moveVerticalCenterTo:(NXCoord *)y
  474. {
  475.     bounds.origin.y = *y - floor(bounds.size.height / 2.0);
  476.     return self;
  477. }
  478.  
  479. - moveBy:(const NXPoint *)offset
  480. {
  481.     bounds.origin.x += floor(offset->x);
  482.     bounds.origin.y += floor(offset->y);
  483.     return self;
  484. }
  485.  
  486. - moveTo:(const NXPoint *)p
  487. {
  488.     bounds.origin.x = floor(p->x);
  489.     bounds.origin.y = floor(p->y);
  490.     return self;
  491. }
  492.  
  493. - centerAt:(const NXPoint *)p
  494. {
  495.     bounds.origin.x = floor(p->x - bounds.size.width / 2.0);
  496.     bounds.origin.y = floor(p->y - bounds.size.height / 2.0);
  497.     return self;
  498. }
  499.  
  500. - sizeTo:(const NXSize *)size
  501. {
  502.     bounds.size.width = floor(size->width);
  503.     bounds.size.height = floor(size->height);
  504.     return self;
  505. }
  506.  
  507. - sizeToNaturalAspectRatio
  508. {
  509.     return [self constrainCorner:UPPER_RIGHT
  510.     toAspectRatio:[self naturalAspectRatio]];
  511. }
  512.  
  513. - sizeToGrid:graphicView
  514. {
  515.     NXPoint p;
  516.  
  517.     [graphicView grid:&bounds.origin];
  518.     p.x = bounds.origin.x + bounds.size.width;
  519.     p.y = bounds.origin.y + bounds.size.height;
  520.     [graphicView grid:&p];
  521.     bounds.size.width = p.x - bounds.origin.x;
  522.     bounds.size.height = p.y - bounds.origin.y;
  523.  
  524.     return self;
  525. }
  526.  
  527. - alignToGrid:graphicView
  528. {
  529.     [graphicView grid:&bounds.origin];
  530.     return self;
  531. }
  532.  
  533. /* Compatibility methods. */
  534.  
  535. - replaceWithImage
  536. {
  537.     return self;
  538. }
  539.  
  540. /* Public routines. */
  541.  
  542. - setLineWidth:(const float *)value
  543. /*
  544.  * This is called with value indirected so that it can be called via
  545.  * a perform:with: method.  Kind of screwy, but ...
  546.  */
  547. {
  548.     if (value) linewidth = *value;
  549.     return self;
  550. }
  551.  
  552. - (float)lineWidth
  553. {
  554.     return linewidth;
  555. }
  556.  
  557. - setLineColor:(NXColor *)color
  558. {
  559.     if (color) {
  560.     if (NXEqualColor(*color, NX_COLORBLACK)) {
  561.         NX_FREE(lineColor);
  562.         lineColor = NULL;
  563.     } else {
  564.         if (!lineColor) NX_ZONEMALLOC([self zone], lineColor, NXColor, 1);
  565.         *lineColor = *color;
  566.     }
  567.     }
  568.     return self;
  569. }
  570.  
  571. - (NXColor)lineColor
  572. {
  573.     return lineColor ? *lineColor : NX_COLORBLACK;
  574. }
  575.  
  576. - setFillColor:(NXColor *)color
  577. {
  578.     if (color) {
  579.     if (NXEqualColor(*color, NX_COLORCLEAR)) {
  580.         NX_FREE(fillColor);
  581.         fillColor = NULL;
  582.     } else {
  583.         if (!fillColor) NX_ZONEMALLOC([self zone], fillColor, NXColor, 1);
  584.         *fillColor = *color;
  585.     }
  586.     }
  587.     return self;
  588. }
  589.  
  590. - (NXColor)fillColor
  591. {
  592.     return fillColor ? *fillColor : NX_COLORCLEAR;
  593. }
  594.  
  595. - setGraphicTextColor:(NXColor *)color
  596. /*
  597.  * By default Graphics don't have a "Text color".
  598.  */
  599. {
  600.     return self;
  601. }
  602.  
  603. - (NXColor)textColor
  604. {
  605.     return NX_COLORCLEAR;
  606. }
  607.  
  608. - changeFont:sender
  609. {
  610.     return self;
  611. }
  612.  
  613. - font
  614. {
  615.     return nil;
  616. }
  617.  
  618. - (BOOL)wantsTextColor
  619. {
  620.     return NO;
  621. }
  622.  
  623. - (BOOL)wantsLineColor
  624. {
  625.     return fillColor ? NO : YES;
  626. }
  627.  
  628. - (BOOL)wantsFillColor
  629. {
  630.     return fillColor ? YES : NO;
  631. }
  632.  
  633. - setGray:(const float *)value
  634. /*
  635.  * This is called with value indirected so that it can be called via
  636.  * a perform:with: method.  Kind of screwy, but ...
  637.  * Now that we have converted to using NXColor's, we'll interpret this
  638.  * method as a request to set the lineColor.
  639.  */
  640. {
  641.     NXColor color;
  642.  
  643.     if (value) {
  644.     color = NXConvertGrayToColor(*value);
  645.     [self setLineColor:&color];
  646.     }
  647.  
  648.     return self;
  649. }
  650.  
  651. - (float)gray
  652. {
  653.     float retval;
  654.  
  655.     if (lineColor) {
  656.     NXConvertColorToGray(*lineColor, &retval);
  657.     } else {
  658.     retval = NX_BLACK;
  659.     }
  660.  
  661.     return retval;
  662. }
  663.  
  664. - setFill:(int)mode
  665. /*
  666.  * If mode = 0 -> don't fill at all, = 1 -> eofill, = 2 -> fill.
  667.  */
  668. {
  669.     switch (mode) {
  670.     case 0: gFlags.eofill = gFlags.fill = NO; break;
  671.     case 1: gFlags.eofill = YES; gFlags.fill = NO; break;
  672.     case 2: gFlags.eofill = NO; gFlags.fill = YES; break;
  673.     }
  674.     if (!mode) {
  675.     NX_FREE(fillColor);
  676.     fillColor = NULL;
  677.     }
  678.     return self;
  679. }
  680.  
  681. - (int)fill
  682. {
  683.     if (!fillColor) {
  684.     return 0;
  685.     } else if (gFlags.eofill) {
  686.     return 1;
  687.     } else if (gFlags.fill) {
  688.     return 2;
  689.     } else {
  690.     return 0;
  691.     }
  692. }
  693.  
  694. - (BOOL)isFilled
  695. {
  696.     return fillColor && (gFlags.fill || gFlags.eofill);
  697. }
  698.  
  699. - setLineCap:(int)capValue
  700. {
  701.     if (capValue >= 0 && capValue <= 2) {
  702.     gFlags.linecap = capValue;
  703.     }
  704.     return self;
  705. }
  706.  
  707. - (int)lineCap
  708. {
  709.     return gFlags.linecap;
  710. }
  711.  
  712. - setLineArrow:(int)arrowValue
  713. {
  714.     if (arrowValue >= 0 && arrowValue <= 3) {
  715.     gFlags.arrow = arrowValue;
  716.     }
  717.     return self;
  718. }
  719.  
  720. - (int)lineArrow
  721. {
  722.     return gFlags.arrow;
  723. }
  724.  
  725. - setLineJoin:(int)joinValue
  726. {
  727.     if (joinValue >= 0 && joinValue <= 2) {
  728.     gFlags.linejoin = joinValue;
  729.     }
  730.     return self;
  731. }
  732.  
  733. - (int)lineJoin
  734. {
  735.     return gFlags.linejoin;
  736. }
  737.  
  738. /* Archiver-related methods. */
  739.  
  740. - write:(NXTypedStream *)stream
  741. /*
  742.  * Since a typical document has many Graphics, we want to try and make
  743.  * the archived document small, so we don't write out the linewidth and
  744.  * gray values if they are the most common 0 and NX_BLACK.  To accomplish
  745.  * this, we note that we haven't written them out by setting the
  746.  * bits in gFlags.
  747.  */
  748. {
  749.     [super write:stream];
  750.     gFlags.linewidthSet = (linewidth != 0.0);
  751.     gFlags.lineColorSet = lineColor ? YES : NO;
  752.     gFlags.fillColorSet = fillColor ? YES : NO;
  753.     NXWriteTypes(stream, "ffffs", &bounds.origin.x, &bounds.origin.y,
  754.     &bounds.size.width, &bounds.size.height, &gFlags);
  755.     if (gFlags.linewidthSet) NXWriteTypes(stream, "f", &linewidth);
  756.     if (gFlags.lineColorSet) NXWriteColor(stream, *lineColor);
  757.     if (gFlags.fillColorSet) NXWriteColor(stream, *fillColor);
  758.     return self;
  759. }
  760.  
  761. - read:(NXTypedStream *)stream
  762. {
  763.     float gray;
  764.     int version;
  765.  
  766.     [super read:stream];
  767.     version = NXTypedStreamClassVersion(stream, "Graphic");
  768.     NXReadTypes(stream, "ffffs", &bounds.origin.x, &bounds.origin.y,
  769.     &bounds.size.width, &bounds.size.height, &gFlags);
  770.     if (gFlags.linewidthSet) NXReadTypes(stream, "f", &linewidth);
  771.     if (version < 1) {
  772.     if (gFlags.lineColorSet) NXReadTypes(stream, "f", &gray);
  773.     if (gFlags.fillColorSet && (gFlags.eofill | gFlags.fill)) {
  774.         NX_ZONEMALLOC([self zone], lineColor, NXColor, 1);
  775.         *lineColor = NXConvertGrayToColor(gray);
  776.         NX_ZONEMALLOC([self zone], fillColor, NXColor, 1);
  777.         *fillColor = NXConvertGrayToColor(gray);
  778.     } else if (gFlags.eofill | gFlags.fill) {
  779.         NX_ZONEMALLOC([self zone], fillColor, NXColor, 1);
  780.         *fillColor = NXConvertGrayToColor(gray);
  781.         NX_ZONEMALLOC([self zone], lineColor, NXColor, 1);
  782.         *lineColor = NX_COLORCLEAR;
  783.     } else {
  784.         NX_ZONEMALLOC([self zone], lineColor, NXColor, 1);
  785.         *lineColor = NXConvertGrayToColor(gray);
  786.     }
  787.     } else {
  788.     if (gFlags.lineColorSet) {
  789.         NX_ZONEMALLOC([self zone], lineColor, NXColor, 1);
  790.         *lineColor = NXReadColor(stream);
  791.     }
  792.     if (gFlags.fillColorSet) {
  793.         NX_ZONEMALLOC([self zone], fillColor, NXColor, 1);
  794.         *fillColor = NXReadColor(stream);
  795.     }
  796.     }
  797.  
  798.     return self;
  799. }
  800.  
  801. /* Routines which may need subclassing for different Graphic types. */
  802.  
  803. - constrainCorner:(int)corner toAspectRatio:(float)aspect
  804. /*
  805.  * Modifies the bounds rectangle by moving the specified corner so that
  806.  * the Graphic maintains the specified aspect ratio.  This is used during
  807.  * constrained resizing.  Can be overridden if the aspect ratio is not
  808.  * sufficient to constrain resizing.
  809.  */
  810. {
  811.     int newcorner;
  812.     float actualAspect;
  813.  
  814.     if (!bounds.size.height || !bounds.size.width || !aspect) return self;
  815.     actualAspect = bounds.size.width / bounds.size.height;
  816.     if (actualAspect == aspect) return self;
  817.  
  818.     switch (corner) {
  819.     case LEFT_SIDE:
  820.     bounds.origin.x -= bounds.size.height * aspect-bounds.size.width;
  821.     case RIGHT_SIDE:
  822.     bounds.size.width = bounds.size.height * aspect;
  823.     if (bounds.size.width) NXIntegralRect(&bounds);
  824.     return self;
  825.     case BOTTOM_SIDE:
  826.     bounds.origin.y -= bounds.size.width / aspect-bounds.size.height;
  827.     case TOP_SIDE:
  828.     bounds.size.height = bounds.size.width / aspect;
  829.     if (bounds.size.height) NXIntegralRect(&bounds);
  830.     return self;
  831.     case LOWER_LEFT:
  832.     corner = 0;
  833.     case 0:
  834.     case UPPER_RIGHT:
  835.     case UPPER_LEFT:
  836.     case LOWER_RIGHT:
  837.     if (actualAspect > aspect) {
  838.         newcorner = ((corner|KNOB_DY_ONCE)&(~(KNOB_DY_TWICE)));
  839.     } else {
  840.         newcorner = ((corner|KNOB_DX_ONCE)&(~(KNOB_DX_TWICE)));
  841.     }
  842.     return [self constrainCorner:newcorner toAspectRatio:aspect];
  843.     default:
  844.     return self;
  845.     }
  846. }
  847.  
  848. #define RESIZE_MASK (NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK|NX_TIMERMASK)
  849.  
  850. - resize:(NXEvent *)event by:(int)corner in:view
  851. /*
  852.  * Resizes the graphic by the specified corner.  If corner == CREATE,
  853.  * then it is resized by the UPPER_RIGHT corner, but the initial size
  854.  * is reset to 1 by 1.
  855.  */
  856. {
  857.     NXPoint p, last;
  858.     float aspect = 0.0;
  859.     id window = [view window];
  860.     BOOL constrain, canScroll;
  861.     DrawStatusType oldDrawStatus;
  862.     NXTrackingTimer *timer = NULL;
  863.     NXRect eb, oldeb, visibleRect;
  864.  
  865.     if (!gFlags.active || !gFlags.selected || !corner) return self;
  866.  
  867.     constrain = ((event->flags & NX_ALTERNATEMASK) &&
  868.     ((bounds.size.width && bounds.size.height) || corner == CREATE));
  869.     if (constrain) aspect = bounds.size.width / bounds.size.height;
  870.     if (corner == CREATE) {
  871.     bounds.size.width = bounds.size.height = 1.0;
  872.     corner = UPPER_RIGHT;
  873.     }
  874.  
  875.     gFlags.selected = NO;
  876.  
  877.     [self getExtendedBounds:&eb];
  878.     [view lockFocus];
  879.     gFlags.active = NO;
  880.     [view cache:&eb];
  881.     gFlags.active = YES;
  882.     [self draw:NULL];
  883.     [window flushWindow];
  884.  
  885.     oldDrawStatus = DrawStatus;
  886.     DrawStatus = Resizing;
  887.  
  888.     [view getVisibleRect:&visibleRect];
  889.     canScroll = !NXEqualRect(&visibleRect, &bounds);
  890.     if (canScroll) startTimer(timer);
  891.  
  892.     while (event->type != NX_MOUSEUP) {
  893.     p = event->location;
  894.     event = [NXApp getNextEvent:RESIZE_MASK];
  895.     if (event->type == NX_TIMER) event->location = p;
  896.     p = event->location;
  897.     [view convertPoint:&p fromView:nil];
  898.     [view grid:&p];
  899.     if (p.x != last.x || p.y != last.y) {
  900.         corner = [self moveCorner:corner to:&p constrain:constrain];
  901.         if (constrain) [self constrainCorner:corner toAspectRatio:aspect];
  902.         oldeb = eb;
  903.         [self getExtendedBounds:&eb];
  904.         [window disableFlushWindow];
  905.         [view drawSelf:&oldeb :1];
  906.         if (canScroll) {
  907.         [view scrollRectToVisible:&bounds];
  908.         [view scrollPointToVisible:&p];
  909.         }
  910.         [self draw:NULL];
  911.         [view tryToPerform:@selector(updateRulers:) with:(void *)&bounds];
  912.         [window reenableFlushWindow];
  913.         [window flushWindow];
  914.         last = p;
  915.         NXPing();
  916.     }
  917.     }
  918.  
  919.     if (canScroll) stopTimer(timer);
  920.  
  921.     gFlags.selected = YES;
  922.     DrawStatus = oldDrawStatus;
  923.  
  924.     [view cache:&eb];
  925.     [view tryToPerform:@selector(updateRulers:) with:nil];
  926.     [window flushWindow];
  927.     [view unlockFocus];
  928.  
  929.     return self;
  930. }
  931.  
  932. - (BOOL)create:(NXEvent *)event in:view
  933. /*
  934.  * This method rarely needs to be subclassed.
  935.  * It sets up an initial bounds, and calls resize:by:in:.
  936.  */
  937. {
  938.     BOOL valid;
  939.     NXCoord gridSpacing;
  940.  
  941.     bounds.origin = event->location;
  942.     [view convertPoint:&bounds.origin fromView:nil];
  943.     [view grid:&bounds.origin];
  944.  
  945.     gridSpacing = (NXCoord)[view gridSpacing];
  946.     bounds.size.height = gridSpacing;
  947.     bounds.size.width = gridSpacing * [self naturalAspectRatio];
  948.  
  949.     [self resize:event by:CREATE in:view];
  950.  
  951.     valid = [self isValid];
  952.  
  953.     if (valid) {
  954.     gFlags.selected = YES;
  955.     gFlags.active = YES;
  956.     } else {
  957.     gFlags.selected = NO;
  958.     gFlags.active = NO;
  959.     [view display];
  960.     }
  961.  
  962.     return valid;
  963. }
  964.  
  965. - (BOOL)hit:(const NXPoint *)p
  966. {
  967.     return(!gFlags.locked && gFlags.active && NXMouseInRect(p, &bounds, NO));
  968. }
  969.  
  970. - (BOOL)isOpaque
  971. {
  972.     return gFlags.fill || gFlags.eofill;
  973. }
  974.  
  975. - (BOOL)isValid
  976. /*
  977.  * Called after a Graphic is created to see if it is valid (this usually
  978.  * means "is it big enough?").
  979.  */
  980. {
  981.     return(bounds.size.width > MINSIZE && bounds.size.height > MINSIZE);
  982. }
  983.  
  984. - (float)naturalAspectRatio
  985. /*
  986.  * A natural aspect ratio of zero means it doesn't have a natural aspect ratio.
  987.  */
  988. {
  989.     return 0.0;
  990. }
  991.  
  992. - (int)moveCorner:(int)corner to:(const NXPoint *)p constrain:(BOOL)flag
  993. /*
  994.  * Moves the specified corner to the specified point.
  995.  * Returns the position of the corner after it was moved.
  996.  */
  997. {
  998.     int newcorner = corner;
  999.  
  1000.     if ((corner & KNOB_DX_ONCE) && (corner & KNOB_DX_TWICE)) {
  1001.     bounds.size.width += p->x - (bounds.origin.x + bounds.size.width);
  1002.     if (bounds.size.width <= 0.0) {
  1003.         newcorner &= ~ (KNOB_DX_ONCE | KNOB_DX_TWICE);
  1004.         bounds.origin.x += bounds.size.width;
  1005.         bounds.size.width = - bounds.size.width;
  1006.     }
  1007.     } else if (!(corner & KNOB_DX_ONCE)) {
  1008.     bounds.size.width += bounds.origin.x - p->x;
  1009.     bounds.origin.x = p->x;
  1010.     if (bounds.size.width <= 0.0) {
  1011.         newcorner |= KNOB_DX_ONCE | KNOB_DX_TWICE;
  1012.         bounds.origin.x += bounds.size.width;
  1013.         bounds.size.width = - bounds.size.width;
  1014.     }
  1015.     }
  1016.  
  1017.     if ((corner & KNOB_DY_ONCE) && (corner & KNOB_DY_TWICE)) {
  1018.     bounds.size.height += p->y - (bounds.origin.y + bounds.size.height);
  1019.     if (bounds.size.height <= 0.0) {
  1020.         newcorner &= ~ (KNOB_DY_ONCE | KNOB_DY_TWICE);
  1021.         bounds.origin.y += bounds.size.height;
  1022.         bounds.size.height = - bounds.size.height;
  1023.     }
  1024.     } else if (!(corner & KNOB_DY_ONCE)) {
  1025.     bounds.size.height += bounds.origin.y - p->y;
  1026.     bounds.origin.y = p->y;
  1027.     if (bounds.size.height <= 0.0) {
  1028.         newcorner |= KNOB_DY_ONCE | KNOB_DY_TWICE;
  1029.         bounds.origin.y += bounds.size.height;
  1030.         bounds.size.height = - bounds.size.height;
  1031.     }
  1032.     }
  1033.  
  1034.     if (newcorner != LOWER_LEFT) newcorner &= 0xf;
  1035.     if (!newcorner) newcorner = LOWER_LEFT;
  1036.  
  1037.     return newcorner;
  1038. }
  1039.  
  1040. - unitDraw
  1041. /*
  1042.  * If a Graphic just wants to draw itself in the bounding box of
  1043.  * {{0.0,0.0},{1.0,1.0}}, it can simply override this method.
  1044.  * Everything else will work fine.
  1045.  */
  1046. {
  1047.     return self;
  1048. }
  1049.  
  1050. - draw
  1051. /*
  1052.  * Almost all Graphics need to override this method.
  1053.  * It does the Graphic-specific drawing.
  1054.  * By default, it scales the coordinate system and calls unitDraw.
  1055.  */
  1056. {
  1057.     if (bounds.size.width >= 1.0 && bounds.size.height >= 1.0) {
  1058.     PStranslate(bounds.origin.x, bounds.origin.y);
  1059.     PSscale(bounds.size.width, bounds.size.height);
  1060.     [self unitDraw];
  1061.     }
  1062.     return self;
  1063. }
  1064.  
  1065. - (BOOL)edit:(NXEvent *)event in:view
  1066. /*
  1067.  * Any Graphic which has editable text should override this method
  1068.  * to edit that text.  TextGraphic is an example.
  1069.  */
  1070. {
  1071.     return NO;
  1072. }
  1073.  
  1074.  
  1075. /* Methods added to support Hippo drawing */
  1076.  
  1077. - forward:(SEL)aSelector :(marg_list)argFrame
  1078. {
  1079.     return self;
  1080. }
  1081. @end
  1082.  
  1083.